package com.google.zxing.client.android.camera;

import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Handler;
import android.view.SurfaceHolder;

import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.client.android.camera.helper.AutoFocusManager;
import com.google.zxing.client.android.camera.helper.CameraConfigurationManager;
import com.google.zxing.client.android.camera.helper.PreviewCallback;
import com.google.zxing.client.android.camera.open.OpenCamera;
import com.google.zxing.client.android.camera.open.OpenCameraInterface;
import com.google.zxing.client.android.helper.CodeManager;

import java.io.IOException;

/**
 * This object wraps the Camera service object and expects to be the only one talking to it. The
 * implementation encapsulates the steps needed to take preview-sized images, which are used for
 * both preview and decoding.
 *
 * @author dswitkin@google.com (Daniel Switkin)
 */
@SuppressWarnings("deprecation") // camera APIs
public final class CameraManager {
	private static final String TAG = CameraManager.class.getSimpleName();
	
	private static final int MIN_FRAME_WIDTH = 240;
	private static final int MIN_FRAME_HEIGHT = 240;
	private static final int MAX_FRAME_WIDTH = 1200; // = 5/8 * 1920
	private static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080
	
	private final CameraConfigurationManager configManager;
	
	private OpenCamera camera;
	
	private AutoFocusManager autoFocusManager;
	
	private Rect framingRect; // 屏幕展示的Rect
	
	private Rect framingRectInPreview; // Camera预览的Rect
	
	private boolean initialized;
	
	private boolean previewing;
	
	private int requestedCameraId = OpenCameraInterface.NO_REQUESTED_CAMERA;
	
	private int requestedFramingRectWidth;
	
	private int requestedFramingRectHeight;
	
	/**
	 * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
	 * clear the handler so it will only receive one message.
	 */
	private final PreviewCallback previewCallback;
	
	private static CameraManager cameraManager;
	
	public static CameraManager getInstance() {
		if (null == cameraManager) {
			synchronized (CameraManager.class) {
				if (null == cameraManager) {
					cameraManager = new CameraManager();
				}
			}
		}
		return cameraManager;
	}
	
	private CameraManager() {
		this.configManager = new CameraConfigurationManager();
		previewCallback = new PreviewCallback(configManager);
	}
	
	/**
	 * Opens the camera driver and initializes the hardware parameters.
	 *
	 * @param holder The surface object which the camera will draw preview frames into.
	 * @throws IOException Indicates the camera driver failed to open.
	 */
	public synchronized void openDriver(SurfaceHolder holder) throws IOException {
		OpenCamera theCamera = camera;
		if (theCamera == null) {
			theCamera = OpenCameraInterface.open(requestedCameraId);
			if (theCamera == null) {
				throw new IOException("Camera.open() failed to return object from driver");
			}
			camera = theCamera;
		}
		
		if (!initialized) {
			initialized = true;
			configManager.initFromCameraParameters(theCamera);
			if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
				setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
				requestedFramingRectWidth = 0;
				requestedFramingRectHeight = 0;
			}
		}
		
		Camera cameraObject = theCamera.getCamera();
		Camera.Parameters parameters = cameraObject.getParameters();
		String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
		try {
			configManager.setDesiredCameraParameters(theCamera, false);
		} catch (RuntimeException re) {
			// Driver failed
			CodeManager.v(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
			CodeManager.v(TAG, "Resetting to saved camera params: " + parametersFlattened);
			// Reset:
			if (parametersFlattened != null) {
				parameters = cameraObject.getParameters();
				parameters.unflatten(parametersFlattened);
				try {
					cameraObject.setParameters(parameters);
					configManager.setDesiredCameraParameters(theCamera, true);
				} catch (RuntimeException re2) {
					// Well, darn. Give up
					CodeManager.v(TAG, "Camera rejected even safe-mode parameters! No configuration");
				}
			}
		}
		cameraObject.setPreviewDisplay(holder);
		
	}
	
	public synchronized boolean isOpen() {
		return camera != null;
	}
	
	/**
	 * Closes the camera driver if still in use.
	 */
	public synchronized void closeDriver() {
		if (camera != null) {
			camera.getCamera().release();
			camera = null;
			// Make sure to clear these each time we close the camera, so that any scanning rect
			// requested by intent is forgotten.
			framingRect = null;
			framingRectInPreview = null;
			
			cameraManager = null; // 释放当前单例
		}
	}
	
	/**
	 * Asks the camera hardware to begin drawing preview frames to the screen.
	 */
	public synchronized void startPreview() {
		OpenCamera theCamera = camera;
		if (theCamera != null && !previewing) {
			theCamera.getCamera().startPreview();
			previewing = true;
			autoFocusManager = new AutoFocusManager(theCamera.getCamera());
		}
	}
	
	/**
	 * Tells the camera to stop drawing preview frames.
	 */
	public synchronized void stopPreview() {
		if (autoFocusManager != null) {
			autoFocusManager.stop();
			autoFocusManager = null;
		}
		if (camera != null && previewing) {
			camera.getCamera().stopPreview();
			previewCallback.setHandler(null, 0);
			previewing = false;
		}
	}
	
	/**
	 * 是否打开灯光，true(打开)
	 * Convenience method for {@link com.google.zxing.client.android.CaptureActivity}
	 *
	 * @param newSetting if {@code true}, light should be turned on if currently off. And vice versa.
	 */
	public synchronized void setTorch(boolean newSetting) {
		OpenCamera theCamera = camera;
		if (theCamera != null && newSetting != configManager.getTorchState(theCamera.getCamera())) {
			boolean wasAutoFocusManager = autoFocusManager != null;
			if (wasAutoFocusManager) {
				autoFocusManager.stop();
				autoFocusManager = null;
			}
			configManager.setTorch(theCamera.getCamera(), newSetting);
			if (wasAutoFocusManager) {
				autoFocusManager = new AutoFocusManager(theCamera.getCamera());
				autoFocusManager.start();
			}
		}
	}
	
	/**
	 * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
	 * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
	 * respectively.
	 *
	 * @param handler The handler to send the message to.
	 * @param message The what field of the message to be sent.
	 */
	public synchronized void requestPreviewFrame(Handler handler, int message) {
		OpenCamera theCamera = camera;
		if (theCamera != null && previewing) {
			previewCallback.setHandler(handler, message);
			theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
		}
	}
	
	/**
	 * 计算UI上，放置二维码的大小
	 * Calculates the framing rect which the UI should draw to show the user where to place the
	 * barcode. This target helps with alignment as well as forces the user to hold the device
	 * far enough away to ensure the image will be in focus.
	 *
	 * @return The rectangle to draw on screen in window coordinates.
	 */
	public synchronized Rect getFramingRect() {
		if (framingRect == null) {
			if (camera == null) {
				return null;
			}
			Point screenResolution = configManager.getScreenResolution();
			if (screenResolution == null) {
				// Called early, before init even finished
				return null;
			}
			
			// 取 屏幕较小值，和，默认教大值
			int shortSide = Math.min(screenResolution.x, screenResolution.y);
			shortSide = Math.max(shortSide, MAX_FRAME_HEIGHT);
			
			int leftOffset = 0;
			int topOffset = (screenResolution.y - shortSide) / 2;
			framingRect = new Rect(leftOffset, topOffset, leftOffset + shortSide, topOffset + shortSide);
			//	int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
			//	int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT);
			//
			//	int leftOffset = (screenResolution.x - width) / 2;
			//	int topOffset = (screenResolution.y - height) / 2;
			//  framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
			CodeManager.v(TAG, "screen display rect: " + framingRect);
		}
		return framingRect;
	}
	
	private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) {
		int dim = 5 * resolution / 8; // Target 5/8 of each dimension
		if (dim < hardMin) {
			return hardMin;
		}
		if (dim > hardMax) {
			return hardMax;
		}
		return dim;
	}
	
	/**
	 * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
	 * not UI / screen.
	 *
	 * @return {@link Rect} expressing barcode scan area in terms of the preview size
	 */
	public synchronized Rect getFramingRectInPreview() {
		if (framingRectInPreview == null) {
			Rect framingRect = getFramingRect();
			if (framingRect == null) {
				return null;
			}
			Rect rect = new Rect(framingRect);
			Point cameraResolution = configManager.getCameraResolution();
			Point screenResolution = configManager.getScreenResolution();
			if (cameraResolution == null || screenResolution == null) {
				// Called early, before init even finished
				return null;
			}
			
			if (screenResolution.x < screenResolution.y) { // 竖屏模式
				rect.left = rect.left * cameraResolution.y / screenResolution.x;
				rect.right = rect.right * cameraResolution.y / screenResolution.x;
				rect.top = rect.top * cameraResolution.x / screenResolution.y;
				rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
			} else { // 横屏模式
				rect.left = rect.left * cameraResolution.x / screenResolution.x;
				rect.right = rect.right * cameraResolution.x / screenResolution.x;
				rect.top = rect.top * cameraResolution.y / screenResolution.y;
				rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
			}
			framingRectInPreview = rect;
			
			CodeManager.v(TAG, "source preview rect: " + framingRectInPreview);
		}
		return framingRectInPreview;
	}
	
	
	/**
	 * 设置拍照的摄像头id，默认 OpenCameraInterface.NO_REQUESTED_CAMERA
	 * 0 --> 后置摄像头{CameraFacing.BACK}
	 * 1 --> 前置摄像头{CameraFacing.FRONT}
	 * Allows third party apps to specify the camera ID, rather than determine
	 * it automatically based on available cameras and their orientation.
	 *
	 * @param cameraId camera ID of the camera to use. A negative value means "no preference".
	 */
	public synchronized void setManualCameraId(int cameraId) {
		requestedCameraId = cameraId;
	}
	
	/**
	 * 手动设置  扫描时的方框范围
	 * Allows third party apps to specify the scanning rectangle dimensions, rather than determine
	 * them automatically based on screen resolution.
	 *
	 * @param width  The width in pixels to scan.
	 * @param height The height in pixels to scan.
	 */
	private synchronized void setManualFramingRect(int width, int height) {
		if (initialized) {
			Point screenResolution = configManager.getScreenResolution();
			if (width > screenResolution.x) {
				width = screenResolution.x;
			}
			if (height > screenResolution.y) {
				height = screenResolution.y;
			}
			int leftOffset = (screenResolution.x - width) / 2;
			int topOffset = (screenResolution.y - height) / 2;
			framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
			CodeManager.v(TAG, "Calculated manual framing rect: " + framingRect);
			framingRectInPreview = null;
		} else {
			requestedFramingRectWidth = width;
			requestedFramingRectHeight = height;
		}
	}
	
	/**
	 * A factory method to build the appropriate LuminanceSource object based on the format
	 * of the preview buffers, as described by Camera.Parameters.
	 *
	 * @param data   A preview frame.
	 * @param width  The width of the image.
	 * @param height The height of the image.
	 * @return A PlanarYUVLuminanceSource instance.
	 */
	public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
		Rect rect = getFramingRectInPreview();
		if (rect == null) {
			return null;
		}
		
		// 横屏和竖屏适配
		Point screenResolution = configManager.getScreenResolution();
		if (screenResolution.x < screenResolution.y) { // 竖屏模式
			byte[] rotatedData = new byte[data.length];
			int newWidth = height;
			int newHeight = width;
			for (int y = 0; y < newWidth; y++) {
				for (int x = 0; x < newHeight; x++)
					rotatedData[x * newWidth + newWidth - 1 - y] = data[x + y * newHeight];
			}
			return new PlanarYUVLuminanceSource(rotatedData, newWidth, newHeight, rect.left, rect.top, rect.width(), rect.height(), false);
		} else { // 横屏模式
			// Go ahead and assume it's YUV rather than die.
			return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height(), false);
		}
	}
}
